/****************************************************************************
 *  QLyrics - A simple lyrics viewer.                                       *
 *  Copyright (C) 2008  hsys <hanshuiys@gmail.com>                          *
 *                                                                          *
 *  This program is free software; you can redistribute it and/or modify    *
 *  it under the terms of the GNU General Public License as published by    *
 *  the Free Software Foundation; either version 3 of the License, or       *
 *  (at your option) any later version.                                     *
 *                                                                          *
 *  This program is distributed in the hope that it will be useful,         *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
 *  GNU General Public License for more details.                            *
 *                                                                          *
 *  You should have received a copy of the GNU General Public License       *
 *  along with this program; if not, write to the Free Software Foundation, *
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA       *
 ****************************************************************************/

#include "lyricsdata.h"
#include <algorithm>
#include <QFile>
#include <QTextCodec>
#include <QTextStream>
#include "configmanager.h"

void LyricsData::applyOffset(LyricsTime offset) {
    // apply the offset tag in .lrc file
    for (int i = 0; i < m_element.size(); i++) {
        int newOffset = m_element.at(i).offset - offset;
        m_element[i].offset = (newOffset < 0) ? 0 : newOffset;
    };
    if (m_element.size() == 0 || (m_element[0].offset > 0 && !m_element[0].lyrics.isEmpty()))
        m_element.insert(m_element.begin(), LyricsElement(0, QString()));
};

void LyricsData::simplify() {
    for (int i = 0; i < m_element.size(); i++) {
        m_element[i].lyrics = m_element[i].lyrics.trimmed();
    };
};

bool LyricsData::loadFromFile(const QString& filename) {
    ConfigManager* cfg = ConfigManager::getInstance();

    // first we test if the file exists and is readable
    if (filename.isEmpty())
        return false;
    QFile fin(filename);
    if (!fin.open(QIODevice::ReadOnly | QIODevice::Text))
        return false;

    // clear existing data
    m_element.clear();
    fin.seek(0);
    m_offset = 0;

    // read every single line
    QTextStream finstream(&fin);
    finstream.setCodec(detectEncoding(fin));
    //finstream.setCodec(cfg->value("Lyrics/Encoding", "GBK").value<QByteArray>());
    QString line;
    while (line = finstream.readLine(), !line.isNull()) {
        processLine(line, finstream);
    };
    fin.close();

    // sort the lyrics lines by their offset
    std::sort(m_element.begin(), m_element.end());

    applyOffset(m_offset);
    simplify();
    m_offset = 0;

    return true;
};

QTextCodec* LyricsData::detectEncoding(QFile& file) {
    ConfigManager* cfg = ConfigManager::getInstance();
    QByteArray c;
    qint64 pos = file.pos();
    file.seek(0);
    while (!file.atEnd()) {
        c += file.readLine();
    };
    file.seek(pos);

    QStringList codecList;
    codecList.append(cfg->value("Lyrics/Encoding", "GBK").value<QByteArray>());
    codecList.append("UTF-8");
    for (int i = 0; i < codecList.size(); i++) {
        QTextCodec* codec = QTextCodec::codecForName(codecList.at(i).toAscii());
        if (codec->canEncode(codec->toUnicode(c))) {
            return codec;
        };
    };
    return QTextCodec::codecForName(codecList.at(0).toAscii());
};

QString LyricsData::processLine(const QString& line, QTextStream& stream) {
    //no tag found, ignored
    int tagStartPos = line.indexOf('[') + 1, tagEndPos = line.indexOf(']', tagStartPos);
    if (tagStartPos == 0 || tagEndPos == -1)
        return line;

    // get the tag string and the value string
    QString tagStr = line.mid(tagStartPos, tagEndPos - 1);
    QString valueStr = processLine(line.mid(tagEndPos + 1), stream);

    tagStartPos = tagStr.indexOf(':');
    // incorrect tag form
    if (tagStartPos == -1)
        return valueStr;

    //check whether the tag is a time tag
    if (tagStr[0] >= '0' && tagStr[0] <= '9') {
        // some lrc generators use ':' to seperate second and millisecond ( =,= )
        // we we have to do some workaround
        if (tagStr.lastIndexOf(':') != tagStartPos) {
            tagStr[tagStr.lastIndexOf(':')] = '.';
        };
        // convert the time to millisecond
        bool flag1, flag2;
        LyricsTime timeMs = tagStr.left(tagStartPos).toInt(&flag1) * 60000 +              // minutes
            tagStr.mid(tagStartPos + 1).toDouble(&flag2) * 1000;          // seconds
        // add the line to the vector
        if (flag1 && flag2)
            m_element.push_back(LyricsElement(timeMs, valueStr));
    } else {
        QString tagName = tagStr.left(tagStartPos);
        QString tagValue = tagStr.mid(tagStartPos + 1);

        if (tagName == "ti") {
            m_track.title = tagValue;
        } else if (tagName == "ar") {
            m_track.artist = tagValue;
        } else if (tagName == "al") {
            m_track.album = tagValue;
        } else if (tagName == "encoding") {
            ConfigManager* cfg = ConfigManager::getInstance();
            if (!cfg->value("Lyrics/ForceEncoding", false).toBool())
                stream.setCodec(tagValue.toAscii());
        } else if (tagName == "offset") {
            m_offset = tagValue.toInt();
        };
    };
    return valueStr;
};

int LyricsData::find(const LyricsTime offset) const {
    // binary search
    std::vector<LyricsElement>::const_iterator l = m_element.begin(), r = m_element.end(), m;
    while (l != r) {
        m = l + (r - l) / 2;
        if (m == l)
            break;
        else if (m->offset <= offset)
            l = m;
        else
            r = m;
    };
    if (l == m_element.end())
        return m_element.size();
    else
        return l - m_element.begin();
};

